/*
 * SIT.c : Simple Interface Toolkit - simple C framework for building GUI
 *         applications on top of Win32, inspired by X Intrinsics API.
 *
 * Copyright (c) Thierry Pierron
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "SIT_P.h"
#include "graphics_P.h"
#include <ctype.h>

/*
 * Programs can be ran both from DOS and Win GUI. In former case, no instance
 * information will be given. Thanks Microsoft for integrating so well DOS interface.
 */
SIT_App app;
APTR    instance;
int     cmd_show, retcode, exiting;
Bool    has_syslink;
Bool    verbose, has_theme;
LPWSTR  sit_class;
pfnCTMA pCoTaskMemAlloc;
pfnCTMF pCoTaskMemFree;
pfnGSFN pGetSaveFileName;
pfnGOFN pGetOpenFileName;
pfnChCo pChooseColor;
pfnChFo pChooseFont;
pfnAlBl pAlphaBlend;
pfnGrFi pGradientFill;
pfnILAd pImageList_Add;
pfnILCe pImageList_Create;
pfnILDe pImageList_Destroy;
pfnILDr pImageList_Draw;
pfnILGI pImageList_GetImageInfo;
pfnILGC pImageList_GetImageCount;
pfnILRP pImageList_Replace;
pfnILRe pImageList_Remove;
pfnIsTA pIsThemeActive;
DWORD   win32vers;

extern int my_main(int, char * argv[]);
extern void IMG_Init(void);

int WINAPI WinMain(HINSTANCE Instance,
                   HINSTANCE PrevInstance,
                   STRPTR    CmdLine,
                   int       CmdShow)
{
	LPWSTR * argv;
	HANDLE   ole32, comdlg, comctl, msimg, uxtheme;
	int      nb, i;

	instance = Instance;
	cmd_show = CmdShow;

	/* CmdLine parameter is not unicode aware even with UNICODE macro set */
	argv = CommandLineToArgvW(GetCommandLine(), &nb);

	/* Convert strings to UTF8 */
	for (i = 0; i < nb; )
	{
		int len = wcslen(argv[i]);
		int sz  = UTF16ToUTF8(NULL, 0, (STRPTR) argv[i], len) + 1;

		/* Parse command line parameters specific to SIT */
		if (wcscmp(argv[i], L"-sit:debug") == 0)
		{
			verbose = 1;
			memmove(argv+i, argv+i+1, nb-i-1); nb --;
			continue;
		}
		else if (wcscmp(argv[i], L"-sit:font") == 0 && i < nb-1)
		{
			GFX_SetSystemFont(argv[i+1]);
			memmove(argv+i, argv+i+2, nb-i-2); nb -= 2;
			continue;
		}
		CmdLine = alloca(sz);

		sz = UTF16ToUTF8(CmdLine, sz, (STRPTR) argv[i], len);

		argv[i++] = (LPWSTR) CmdLine;
	}

	srand(time(NULL));

	/*
	 * We are using only a few functions, dynamicly load those libraries to avoid
	 * to specify object files for programs that uses static link.
	 */
	ole32 = LoadLibrary(L"ole32.dll");
	if (ole32)
	{
		WINAPI HRESULT (*coinit)(PVOID);
		coinit = (APTR) GetProcAddress(ole32, "CoInitialize");
		coinit(NULL);

		pCoTaskMemAlloc = (APTR) GetProcAddress(ole32, "CoTaskMemAlloc");
		pCoTaskMemFree  = (APTR) GetProcAddress(ole32, "CoTaskMemFree");
	}
	comdlg = LoadLibrary(L"comdlg32.dll");
	if (comdlg)
	{
		pGetSaveFileName = (APTR) GetProcAddress(comdlg, "GetSaveFileNameW");
		pGetOpenFileName = (APTR) GetProcAddress(comdlg, "GetOpenFileNameW");
		pChooseColor     = (APTR) GetProcAddress(comdlg, "ChooseColorW");
		pChooseFont      = (APTR) GetProcAddress(comdlg, "ChooseFontW");
	}
	comctl = LoadLibrary(L"comctl32.dll");
	if (comctl)
	{
		INITCOMMONCONTROLSEX init = {sizeof init, ICC_LINK_CLASS};
		WINAPI void (*pInitCommonControls)(void);
		WINAPI BOOL (*pInitCommonControlsEx)(LPINITCOMMONCONTROLSEX);

		pInitCommonControls      = (APTR) GetProcAddress(comctl, "InitCommonControls");
		pInitCommonControlsEx    = (APTR) GetProcAddress(comctl, "InitCommonControlsEx");
		pImageList_Add           = (APTR) GetProcAddress(comctl, "ImageList_Add");
		pImageList_Create        = (APTR) GetProcAddress(comctl, "ImageList_Create");
		pImageList_Destroy       = (APTR) GetProcAddress(comctl, "ImageList_Destroy");
		pImageList_Remove        = (APTR) GetProcAddress(comctl, "ImageList_Remove");
		pImageList_Draw          = (APTR) GetProcAddress(comctl, "ImageList_Draw");
		pImageList_GetImageInfo  = (APTR) GetProcAddress(comctl, "ImageList_GetImageInfo");
		pImageList_GetImageCount = (APTR) GetProcAddress(comctl, "ImageList_GetImageCount");
		pImageList_Replace       = (APTR) GetProcAddress(comctl, "ImageList_Replace");

		pInitCommonControls();
		has_syslink = pInitCommonControlsEx(&init);
	}
	msimg = LoadLibrary(L"msimg32.dll");
	if (msimg)
	{
		pAlphaBlend = (APTR) GetProcAddress(msimg, "AlphaBlend");
		pGradientFill = (APTR) GetProcAddress(msimg, "GradientFill");
	}
	uxtheme = LoadLibrary(L"uxtheme.dll");
	if (uxtheme)
	{
		pIsThemeActive = (APTR) GetProcAddress(uxtheme, "IsThemeActive");
		has_theme = pIsThemeActive();
	}

	/* We will have to circumvent known bugs */
	{
		OSVERSIONINFO version = {.dwOSVersionInfoSize = sizeof version};
		GetVersionEx(&version);

		/* Store the version similar to the WINVER macro */
		win32vers = (version.dwMajorVersion << 8) | MIN(version.dwMinorVersion, 0xff);
	}

	IMG_Init();
	sit_class = (LPWSTR) (ULONG) GlobalAddAtom(L"sit.superclass");
	/* Program entry point */
	nb = my_main(nb, (STRPTR *) argv);

	GlobalDeleteAtom((ATOM) (DWORD) sit_class);
	LocalFree(argv);
	FreeLibrary(ole32);
	FreeLibrary(comctl);
	FreeLibrary(comdlg);
	FreeLibrary(msimg);
	FreeLibrary(uxtheme);

	return nb;
}

DLLIMP int SIT_Main(void)
{
	MSG msg;

	while (!exiting && GetMessage(&msg, NULL, 0, 0))
	{
		SIT_Widget w = (SIT_Widget) GetProp(msg.hwnd, sit_class);

		if (w)
		{
			#define	dlg    ((SIT_Dialog)w)
			while (w && w->sw_Type != SIT_DIALOG)
				w = w->sw_Parent;

			/* Tooltip is being displayed */
			if (dlg->sd_Tooltip && (msg.message == WM_LBUTTONDOWN || (msg.message == WM_KEYDOWN &&
			    msg.wParam == VK_ESCAPE)))
			{
				ReleaseCapture();
				SendMessage(dlg->sd_Tooltip->sw_Handle, TTM_POP, 0, 0);
				dlg->sd_Tooltip = NULL;
				if (dlg->sd_ToolTimerID > 0)
					SIT_StopTimer(dlg->sd_ToolTimerID), dlg->sd_ToolTimerID = 0;
			}

			/* Mouse wheel message: focus will follow mouse */
			if (msg.message == WM_MOUSEWHEEL)
			{
				SIT_Widget sub;
				POINT pt = msg.pt, off = {};
				ScreenToClient(w->sw_Handle, &pt);

				for (sub = HEAD(w->sw_Children); sub; )
				{
					if (sub->sw_Visible &&
					    sub->sw_Box.x + off.x <= pt.x && pt.x < sub->sw_Box.width  + off.x &&
					    sub->sw_Box.y + off.y <= pt.y && pt.y < sub->sw_Box.height + off.y)
					{
						msg.hwnd = sub->sw_Handle;
						if (sub->sw_Children.lh_Count > 0)
							off.x += sub->sw_Box.x, off.y += sub->sw_Box.y, sub = HEAD(sub->sw_Children);
						else
							break;
					}
					else NEXT(sub);
				}
				if (msg.hwnd != dlg->sd_WheelWnd) dlg->sd_ScrollWheel = 0;
			}

			if (dlg->sd_Accel && TranslateAccelerator(w->sw_Handle, dlg->sd_Accel, &msg))
				continue;

			/* Auto-remove focus from editbox when clicking outside */
			if (dlg->sd_Capture && dlg->sd_Capture != msg.hwnd)
			{
				switch (msg.message) {
				case WM_RBUTTONDOWN:
				case WM_LBUTTONDOWN:
				case WM_MBUTTONDOWN:
					SetFocus(msg.hwnd);
				}
			}

			/* Will handle tab like a DialogProc */
			if (IsDialogMessage(w->sw_Handle, &msg))
				continue;
		}
		else if (msg.message == WM_NEWINSTANCE)
		{
			/* Single instance notification */
			void SIT_AppNotify(SIT_App);
			SIT_AppNotify(app);
			continue;
		}
		/* Translate virtual-key messages into character messages */
		TranslateMessage(&msg);
		/* Send message to WindowProcedure */
		DispatchMessage(&msg);
	}
	if (msg.message == WM_QUIT)
	{
		/* Only message that returns false */
		retcode = msg.wParam;
		exiting = True;
	}
	return retcode;
}

static int SIT_MessageBox(SIT_Widget w, STRPTR text, STRPTR title, int flags)
{
	LPWSTR text16, title16;

	UTF8ToUTF16(text, text16);
	UTF8ToUTF16(title, title16);

	return MessageBox(w ? SIT_GetContainer(w) : NULL, text16, title16, flags);
}

/* Report an error and quit */
DLLIMP void SIT_Log(int level, STRPTR fmt, ...)
{
	static int flags[] = {
		MB_OK | MB_ICONERROR,
		MB_OK | MB_ICONERROR,
		MB_OK | MB_ICONWARNING,
		MB_OK | MB_ICONINFORMATION
	};
	TEXT    buffer[256];
	va_list args;

	va_start(args, fmt);
	switch (level) {
	case SIT_DEBUG:
		vfprintf(stderr, fmt, args);
		break;
	case SIT_CRITICAL:
	case SIT_ERROR:
	case SIT_WARN:
	case SIT_INFO:
		vsprintf(buffer, fmt, args);
		SIT_MessageBox(NULL, buffer, "Error", flags[level-SIT_CRITICAL]);
		break;
	}
	va_end(args);

	if (level == SIT_CRITICAL)
		exit(1);
}

DLLIMP int SIT_Ask(SIT_Widget w, STRPTR title, int buttons, STRPTR fmt, ...)
{
	SIT_Widget d;
	LPWSTR     wtitle, msg;
	int        flags = 0;
	switch (buttons) {
	case SITV_YesNo:       flags = MB_YESNO; break;
	case SITV_YesNoCancel: flags = MB_YESNOCANCEL; break;
	case SITV_OkCancel:    flags = MB_OKCANCEL;
	}
	flags |= MB_ICONQUESTION;

	va_list args;
	va_start(args, fmt);
	fmt = RawDoFmt(fmt, &args);
	va_end(args);
	UTF8ToUTF16(fmt, msg);
	free(fmt);

	if (title == NULL)
	{
		for (d = w; d && d->sw_Type != SIT_DIALOG; d = d->sw_Parent);
		if (d && d->sw_Title)
			UTF8ToUTF16(d->sw_Title, wtitle)
		else
			wtitle = app->super.sw_Name;
	}
	else UTF8ToUTF16(title, wtitle);

	switch (MessageBox(w ? w->sw_Handle : NULL, msg, wtitle, flags | MB_ICONQUESTION)) {
	case IDCANCEL: return -1;
	case IDOK:
	case IDYES:
	case IDRETRY:  return  1;
	}
	return 0;
}

/* Exit from main loop */
DLLIMP void SIT_Exit(int code)
{
	PostQuitMessage(code);       /* send a WM_QUIT to the message queue */
}

DLLIMP void SIT_Notify(SIT_Widget w, APTR data)
{
	/* This way is thead safe to call SIT_Notify from another thread */
	if (w) PostMessage(w->sw_Handle, WM_ONUSER, 0, (LPARAM) data);
}

DLLIMP void SIT_SetFocus(SIT_Widget w)
{
	if (w) SetFocus(w->sw_Handle);
}

DLLIMP SIT_Widget SIT_GetFocus(void)
{
	HWND wnd = GetFocus();

	return (SIT_Widget) GetProp(wnd, sit_class);
}

DLLIMP void SIT_Refresh(SIT_Widget w, int x, int y, int width, int height, Bool erase)
{
	RECT r = {x, y, x + width, y + height};

	if (width  == 0) r.right  = w->sw_Box.width - x;
	if (height == 0) r.bottom = w->sw_Box.height - y;

	InvalidateRect(w->sw_Handle, &r, erase);
}

/* Find a control using breadth-first iterative (ie:non-recursive) search */
APTR SIT_FindControl(SIT_Widget parent, STRPTR utf8, int len, Bool recursive)
{
	LPWSTR     name = alloca(len * sizeof *name);
	SIT_Widget root = parent, c;

	if (len <= 1) return NULL;

	len = MultiByteToWideChar(CP_UTF8, 0, utf8, len-1, name, len);
	name[len] = 0;

	if (wcscmp(parent->sw_Name, name) == 0) return parent;

	for (;;)
	{
		for (c = HEAD(parent->sw_Children); c && wcscmp(c->sw_Name, name); NEXT(c));

		if (c == NULL)
		{
			if (parent->sw_Type == SIT_DIALOG && (c = ((SIT_Dialog)parent)->sd_Status) && wcscmp(c->sw_Name, name) == 0)
				return c;

			if (! recursive) return NULL;

			for (c = HEAD(parent->sw_Children); ; c = (APTR) parent->sw_Node.ln_Next, parent = parent->sw_Parent)
			{
				for (; c && (c->sw_Children.lh_Count == 0 || (c->sw_Flags & SITF_TopLevel)); NEXT(c));
				if (c) { parent = c; break; }
				if (root == parent) return NULL;
			}
		}
		else return c;
	}
}

DLLIMP SIT_Widget SIT_GetById(SIT_Widget parent, STRPTR utf8name)
{
	return SIT_FindControl(parent, utf8name, strlen(utf8name) + 1, True);
}
